package com.panopset;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * Useful String values.
 *
 * @author Karl Dinwiddie
 *
 */
public final class Strings {

    /**
     * End of line.
     */
    private static String eol;

    /**
     *
     * @param value
     *            Value to split.
     * @param regex
     *            Regular expression to split value on.
     * @return ArrayList of values.
     */
    public static List<String> splitOutString(final String value,
            final String regex) {
        final String[] sa = value.split(regex);
        List<String> rtn = new ArrayList<String>();
        for (String s : sa) {
            rtn.add(s);
        }
        return rtn;
    }

    /**
     * inverse of splitOutString.
     *
     * @param values
     *            List of values to splice in.
     * @param regex
     *            Regular expression to eventually split value on.
     * @return values concatenated with regex.
     */
    public static String spliceInString(final List<String> values,
            final String regex) {
        StringWriter sw = new StringWriter();
        boolean firstTime = true;
        for (String s : values) {
            if (firstTime) {
                firstTime = false;
            } else {
                sw.append(regex);
            }
            sw.append(s);
        }
        return sw.toString();
    }

    /**
     * Prompt user from the command line for a String.
     *
     * @param prompt
     *            Prompt.
     * @return User inputed String.
     */
    public static String promptUserFromCommmandLine(final String prompt) {
        if (prompt == null) {
            return "";
        }
        System.out.print(prompt);
        BufferedReader br = new BufferedReader(

        new InputStreamReader(System.in));
        String rtn = null;
        try {
            rtn = br.readLine();
        } catch (IOException ioe) {
            return "";
        }
        return rtn;
    }

    /**
     * Defaults to Carriage Return and Line Feed.
     *
     * @return Default value or value set by setEol.
     */
    public static String getEol() {
        if (eol == null) {
            eol = DOS_RTN;
        }
        return eol;
    }

    /**
     * Guarantee a populated String.
     *
     * @param value
     *            Value.
     * @return Value passed or empty String if value is null.
     */
    public static String populate(final String value) {
        if (value == null) {
            return "";
        }
        return value;
    }

    /**
     * Rarely used.
     *
     * @param value
     *            New end of line, or line feed value.
     */
    public static void setEol(final String value) {
        eol = value;
    }

    /**
     * UTF8.
     */
    public static final String UTF_8 = "UTF8";

    /**
     * Java system property <b>path.separator</b>.
     */
    public static final String PATH_SEP = System.getProperty("path.separator");

    /**
     * Forward slash.
     */
    public static final String FILE_SEP = "/";

    /**
     * Java system property <b>user.dir</b>.
     */
    public static final String USER_DIR = System.getProperty("user.dir");

    /**
     * Line feed value.
     */
    public static final int LINE_FEED_VALUE = 10;

    /**
     * (char) 10 or 0xA.
     */
    public static final String LINE_FEED = "" + (char) LINE_FEED_VALUE;

    /**
     * Line feed value.
     */
    public static final int CARRIAGE_RETURN_VALUE = 13;

    /**
     * Decimal 13 or 0xC.
     */
    public static final String CARRIAGE_RETURN = ""
            + (char) CARRIAGE_RETURN_VALUE;

    /**
     * 0x0C0A.
     */
    public static final String DOS_RTN = CARRIAGE_RETURN + LINE_FEED;

    /**
     * Java system property <b>line.separator</b>.
     */
    public static final String RTN = System.getProperty("line.separator");

    /**
     * Java system property <b>user.home</b>.
     */
    public static final String USER_HOME = System.getProperty("user.home");

    /**
     * Panopset user directory <b>.com.panopset</b>.
     */
    public static final String PANSOPSET_USER_DIR = USER_HOME + FILE_SEP
            + ".com/panopset";

    /**
     * Tab character.
     */
    public static final String TAB = "" + '\u0009';

    /**
     * <b>.xml</b>.
     */
    public static final String XML_SFX = ".xml";

    /**
     * Java return character.
     */
    public static final String JAVA_RTN = "\n";

    /**
     * Copyright character.
     */
    public static final String COPYRIGHT = "\u00a9";

    /**
     * @param s
     *            String to lower case first letter of.
     * @return s with first character set to lower case.
     */
    public static String lowerCaseFirstLetter(final String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        if (s.length() == 1) {
            return s.substring(0, 1).toLowerCase();
        }
        return s.substring(0, 1).toLowerCase() + s.substring(1);
    }

    /**
     * @param s
     *            String to upper case first letter of.
     * @return s with first character set to upper case.
     */
    public static String upperCaseFirstLetter(final String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        if (s.length() == 1) {
            return s.substring(0, 1).toUpperCase();
        }
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }

    /**
     * Fully replace a String.
     *
     * <pre>
     * fullyReplace(&quot;   foo  &quote;, &quot; &quot;, &quot;&quot;)
     * </pre>
     *
     * gives you
     *
     * <pre>
     * &quot;foo&quot;
     * </pre>
     *
     * @param src
     *            Source String.
     * @param from
     *            From String.
     * @param to
     *            To String.
     * @return src recursively replaced from with to.
     */
    public static String fullyReplace(final String src, final String from,
            final String to) {
        if (src == null || from == null || to == null) {
            return "";
        }
        String rtn = src.replace(from, to);
        if (rtn.length() == src.length()) {
            return rtn;
        }
        return fullyReplace(rtn, from, to);
    }

    /**
     * @param s
     *            String
     * @return s or empty string if s is null.
     */
    public static String getValue(final String s) {
        if (s == null) {
            return "";
        }
        return s;
    }

    /**
     * Return true if the passed String is not null or empty.
     *
     * @param s
     *            String to test.
     * @return false if null or empty String, true otherwise
     */
    public static boolean isPopulated(final String s) {
        return s != null && !"".equals(s);
    }

    /**
     * Return true if the passed String array has all elements populated with
     * non-blank values..
     *
     * @param s
     *            String to test.
     * @return false if null or empty String, true otherwise
     */
    public static boolean isPopulated(final String[] s) {
        if (s == null) {
            return false;
        }
        for (int i = 0; i < s.length; i++) {
            if (!isPopulated(s[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Split a String by a cut value.
     *
     * @param s
     *            String to split.
     * @param cut
     *            Cut value.
     * @return rtn[0] = String before the cut value, rtn[1] = String after the
     *         cut value.
     */
    public static String[] split(final String s, final String cut) {
        String[] rtn = new String[] {"", ""};
        if (!isPopulated(s)) {
            return rtn;
        }
        if (!isPopulated(cut)) {
            return rtn;
        }
        if (cut.length() > s.length()) {
            return rtn;
        }
        int i = s.indexOf(cut);
        if (i > 0 && s.length() > i + cut.length()) {
            rtn[0] = s.substring(0, i);
            rtn[1] = s.substring(i + cut.length());
            return rtn;
        }
        return rtn;
    }

    /**
     * @param s
     *            String.
     * @return First line of given String s, or empty.
     */
    public static String getFirstLine(final String s) {
        if (Strings.isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                return br.readLine();
            } catch (IOException e) {
                Util.log(e);
            } finally {
                try {
                    br.close();
                } catch (IOException e) {
                    Util.log(e);
                }
            }
        }
        return "";
    }

    /**
     * Get lines from a String.
     *
     * @param s
     *            String to get lines from.
     * @return List of each line from a String.
     */
    public static List<String> getLines(final String s) {
        List<String> rtn = new ArrayList<String>();
        StringReader sr = new StringReader(s);
        BufferedReader br = new BufferedReader(sr);
        try {
            String line = br.readLine();
            while (line != null) {
                rtn.add(line);
                line = br.readLine();
            }
        } catch (IOException e) {
            Util.log(e);
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                Util.log(e);
            }
        }
        return rtn;
    }

    /**
     * If you have a String that you want all but the first appearance of a
     * String replaced with another String, use this method.
     *
     * If s is null, an empty String will be returned.
     *
     * Examples, from the StringsTest JUnit test:
     *
     * <code>
     *  result    s       from to
        a#b@c@d = a#b#c#d #    @
        a#b@c   = a#b#c   #    @
        a#b     = a#b     #    @
        ab      = ab      #    @
        #ab     = #ab     #    @
        #@ab    = ##ab    #    @
        #@@@ab  = ####ab  #    @
     * </code>
     *
     * @param s
     *            String to modify.
     * @param from
     *            String to replace all but first instance of within s.
     * @param to
     *            Replacement String.
     * @return Modified String.
     */
    public static String replaceFollowingStrings(final String s,
            final String from, final String to) {
        if (s == null) {
            return "";
        }
        boolean firstTime = true;
        String t = s;
        StringWriter sw = new StringWriter();
        int i = t.indexOf(from);
        while (i > -1) {
            sw.append(t.substring(0, i));
            if (firstTime) {
                firstTime = false;
                sw.append(from);
            } else {
                sw.append(to);
            }
            t = t.substring(i + 1);
            i = t.indexOf(from);
        }
        sw.append(t);
        return sw.toString();
    }

    /**
     * Prevent instantiation.
     */
    private Strings() {
    }

    /**
     * Compare dot separated version String.
     *
     * @param version
     *            Version to test.
     * @param existing
     *            Existing version.
     * @return true if version is greater or same as existing version.
     */
    public static boolean isNewerDotVersion(final String version,
            final String existing) {
        return isNewerVersion(version, existing, ".");
    }

    /**
     * -SNAPSHOT.
     */
    public static final String SNAPSHOT = "-SNAPSHOT";

    /**
     * Compare dot separated version String. <b>1.0-SNAPSHOT</b> is considered
     * newer than <b>1.0</b>. <b>1.16</b> is newer than <b>1.3</b>. When the
     * versions are the same, true is also returned.
     *
     * @param version
     *            Version to test.
     * @param existing
     *            Existing version.
     * @param separator
     *            Version String separator (dot, dash, whatever).
     * @return true if version is greater than existing version.
     */
    public static boolean isNewerVersion(final String version,
            final String existing, final String separator) {
        if (separator.length() != 1) {
            throw new RuntimeException(
                    "Code error, only length 1 separators allowed.");
        }
        String v0 = version;
        String v1 = existing;
        boolean ist0snapshot = false;
        boolean ist1snapshot = false;
        int s0i = v0.indexOf(SNAPSHOT);
        int s1i = v1.indexOf(SNAPSHOT);
        if (s0i > -1) {
            ist0snapshot = true;
            v0 = v0.substring(0, s0i);
        }
        if (s1i > -1) {
            ist1snapshot = true;
            v1 = v1.substring(0, s1i);
        }
        StringTokenizer s0 = new StringTokenizer(v0, separator);
        StringTokenizer s1 = new StringTokenizer(v1, separator);
        while (s1.hasMoreTokens()) {
            if (!s0.hasMoreTokens()) {
                return false;
            }
            String t0 = s0.nextToken();
            String t1 = s1.nextToken();
            try {
                Integer i0 = Integer.parseInt(t0);
                Integer i1 = Integer.parseInt(t1);
                if (i0 > i1) {
                    return true;
                } else if (i0 < i1) {
                    return false;
                }
            } catch (NumberFormatException ex) {
                return false;
            }
            if (t0.compareTo(t1) > 0) {
                return true;
            } else if (t0.compareTo(t1) < 0) {
                return false;
            }
        }
        if (ist1snapshot && !ist0snapshot) {
            return false;
        }
        return true;
    }

    /**
     * Capitalize first letter in a String.
     *
     * @param s
     *            String to capitalize
     * @return Capitalized value.
     */
    public static String capitalize(final String s) {
        if (s != null && s.length() > 0) {
            if (s.length() > 1) {
                return ("" + s.charAt(0)).toUpperCase() + s.substring(1);
            }
            return s.toUpperCase();
        }
        return "";
    }

    /**
     * String.toUpperCase wrapper.
     *
     * @param s
     *            String to set to upper case.
     * @return Upper case representation of s.
     */
    public static String toUpperCase(final String s) {
        return s.toUpperCase();
    }

    /**
     * Given a List of Strings, return a List of Strings with any extensions
     * dropped off. ie: x.txt becomes x.
     *
     * @param list
     *            List.
     * @return List&lt;String&gt;
     */
    public static List<String> dropExtensions(final List<String> list) {
        List<String> rtn = new ArrayList<String>();
        for (String s : list) {
            rtn.add(dropExtension(s));
        }
        return rtn;
    }

    /**
     * Drop path from String, /foo/bar becomes bar.
     *
     * @param s
     *            If empty or null, an empty String will be returned.
     * @return String with paths dropped off.
     */
    public static String dropPath(final String s) {
        if (!isPopulated(s)) {
            return "";
        }
        int i = s.lastIndexOf("/");
        if (i > -1 && s.length() > i) {
            return s.substring(i + 1);
        }
        return s;
    }

    /**
     * Drop extension from String, x.txt becomes x.
     *
     * @param s
     *            If empty or null, an empty String will be returned.
     * @return String with extension dropped off.
     */
    public static String dropExtension(final String s) {
        if (!isPopulated(s)) {
            return "";
        }
        int i = s.lastIndexOf(".");
        if (i > -1) {
            return s.substring(0, i);
        }
        return s;
    }

    /**
     * Get file extension, ie <b>.txt</b>.
     *
     * @param f
     *            File.
     * @return Extension, if any, or blank.
     */
    public static String getExtension(final File f) {
        return getExtension(Util.getCanonicalPath(f));
    }

    /**
     * Get extension from String, ie <b>.txt</b>.
     *
     * @param s
     *            String to check.
     * @return Extension, if any, or blank.
     */
    public static String getExtension(final String s) {
        if (Strings.isPopulated(s)) {
            int i = s.lastIndexOf(".");
            if (i > -1) {
                return s.substring(i);
            }
        }
        return "";
    }

    /**
     * Get property value from a String representation of a property as stored
     * as a Property file line.
     *
     * @param src
     *            String representation of a property as stored, ie: a=b
     * @return Everything after the = sign, or an empty String object if src is
     *         null or not in a property format.
     */
    public static String getPropFromStringRep(final String src) {
        if (Strings.isPopulated(src)) {
            int i = src.indexOf("=");
            if (i > -1) {
                String spliced = src.substring(i);
                if (spliced.length() > 1) {
                    return spliced.substring(1);
                }
            }
        }
        return "";
    }

    /**
     *
     * Get key from String representation of name value pair.
     *
     * @param src
     *            Source.
     * @param sep
     *            Separator.
     * @return The key.
     */
    public static String getKeyFromStringRep(final String src,
            final String sep) {
        if (Strings.isPopulated(src)) {
            int i = src.indexOf(sep);
            if (i > -1) {
                return (src.substring(0, i));
            }
        }
        return "";
    }
    /**
     * Get the keys of a text representation of a properties file. Line values
     * are expected to be separated by an '=' sign.
     *
     * @param s
     *            String to parse in to lines.
     * @return List<String> of keys
     */
    public static List<String> stringLineKeys(final String s) {
        return stringLineKeys(s, "=");
    }

    /**
     * Get the keys of a text representation of a properties file. Rarely used,
     * typically the separator is '=' and the stringLineKeys(String) method
     * would be used.
     *
     * @param s
     *            String to parse in to lines.
     * @param separator
     *            Separator String.
     * @return List<String> of keys
     */
    public static List<String> stringLineKeys(final String s,
            final String separator) {
        List<String> v = new ArrayList<String>();
        if (Strings.isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                String rs = br.readLine();
                while (rs != null) {
                    v.add(Strings.getKeyFromStringRep(rs, separator));
                    rs = br.readLine();
                }
            } catch (IOException ex) {
                Util.log(ex);
            }
        }
        return v;
    }

    /**
     * @param s
     *            String to count lines in.
     * @return Count of lines in s.
     */
    public static int lineCount(final String s) {
        if (Strings.isPopulated(s)) {
            List<String> v = stringLines(s);
            if (v != null) {
                return v.size();
            }
        }
        return -1;
    }

    /**
     * @param s
     *            String to count lines.
     * @return Count of lines in s, not including blank lines.
     */
    public static int lineCountSkipBlanks(final String s) {
        if (Strings.isPopulated(s)) {
            List<String> v = stringLinesSkipBlanks(s);
            if (v != null) {
                return v.size();
            }
        }
        return -1;
    }

    /**
     * Convert a String to a List<String> separated by return characters.
     *
     * @param s
     *            String to parse in to lines.
     * @return List<String> of lines of text.
     */
    public static List<String> stringLines(final String s) {
        List<String> v = new ArrayList<String>();
        if (Strings.isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                String rs = br.readLine();
                while (rs != null) {
                    v.add(rs);
                    rs = br.readLine();
                }
            } catch (IOException ex) {
                Util.log(ex);
            }
        }
        return v;
    }

    /**
     * Convert a String to a List<String> separated by return characters.
     * Ignore empty lines.
     *
     * @param s
     *            String to parse in to lines.
     * @return List<String> of lines of text.
     */
    public static List<String> stringLinesSkipBlanks(final String s) {
        List<String> v = new ArrayList<String>();
        if (Strings.isPopulated(s)) {
            StringReader sr = new StringReader(s);
            BufferedReader br = new BufferedReader(sr);
            try {
                String rs = br.readLine();
                while (rs != null) {
                    if (Strings.isPopulated(rs.trim())) {
                        v.add(rs);
                    }
                    rs = br.readLine();
                }
            } catch (IOException ex) {
                Util.log(ex);
            }
        }
        return v;
    }

    /**
     * @param sourceList Source list.
     * @return List as Strings.
     */
    public static String list2str(final List<String> sourceList) {
        if (sourceList == null || sourceList.isEmpty()) {
            return "";
        }
        StringWriter sw = new StringWriter();
        boolean firstTime = true;
        for (String s : sourceList) {
            if (firstTime) {
                firstTime = false;
            } else {
                sw.append(getEol());
            }
            sw.append(s);
        }
        return sw.toString();
    }
}
